import WebGl from '@/modules/three';
import { SUBTRACTION, ADDITION, Brush, Evaluator } from 'three-bvh-csg';
const { Three, TWEEN, dat, OrbitControls, OBJLoader, MTLLoader } = WebGl;

const btnW = 10,
  btnH = 5;
const bgW = btnW * 2,
  bgH = btnH * 2;
class WebGlControl {
  private container: any;
  private camera: any;
  private scene: any;
  private renderer: any;
  private controls: any;
  private animateFrame: any;
  private windowHalfX = window.innerWidth / 2;
  private windowHalfY = window.innerHeight / 2;
  private raycaster: any;
  private pointer: any;
  private buttonGroup: any;
  private pointLight: any;

  private evaluator: any;
  private brush: any;
  private resultBrush: any;

  private buttonState: 0 | 1 = 0;
  private buttonAniVal: { val: number } = { val: 0 };

  private lights: {
    ambiLigth?: any;
    pointLight?: any;
    directLight?: any;
  } = {};
  private Geometrys: {
    cubeGeometry?: any;
    ballGeometry?: any;
  } = {}; //保存所有Geometry，方便统一销毁
  private Materials: {
    cubeMaterial?: any;
    ballMaterial?: any;
    btnMaterial?: any;
  } = {}; //保存所有Material，方便统一销毁
  private Meshs: {
    raycasterPlatform?: any;
    ball?: any;
    clouds?: any;
    stars?: any;
  } = {};

  private GuiInit: any = null;
  private guiData: any = {
    x: 0.005,
    btnCo1: new Three.Color('#201f33'),
    btnCo2: new Three.Color('#8b9fd7'),
    autoRotate: true,
  };
  private mouseData: any = {
    mouseX: 0,
    mouseY: 0,
  };
  private ballPosition: { x: number; z: number } = {
    x: -btnH / 2, //左-btnH/2 右btnH/2
    // y: 0,
    z: btnH * 0.5 + 0.1,
  };

  public init = (id: string) => {
    // if(this.scene) {return}
    this.container = document.getElementById(id);
    let width = this.container.clientWidth,
      height = this.container.clientHeight;

    this.scene = new Three.Scene();
    // var envBackground1 = new Three.TextureLoader().load('../textures/Sunset_Over_Rocks.jpg');
    // this.scene.background = envBackground1;
    const loader = new Three.CubeTextureLoader();
    loader.setPath('../textures/');
    const bgTexture1 = loader.load([
      'posx.jpg',
      'negx.jpg',
      'posy.jpg',
      'negy.jpg',
      'posz.jpg',
      'negz.jpg',
    ]);
    bgTexture1.colorSpace = Three.SRGBColorSpace;
    this.scene.background = bgTexture1;
    this.scene.rotation.y = 1.6;

    this.camera = new Three.PerspectiveCamera(45, width / height, 1, 100);
    this.camera.position.x = 30;
    this.camera.position.y = 0;
    this.camera.position.z = 0;
    this.camera.lookAt(this.scene.position);

    this.renderer = new Three.WebGLRenderer();
    // this.renderer.setClearColor( 0xffffff );
    this.renderer.setPixelRatio(window.devicePixelRatio);
    this.renderer.setSize(width, height);
    this.renderer.shadowMap.enabled = true;
    this.container.appendChild(this.renderer.domElement);

    this.controls = new OrbitControls(this.camera, this.renderer.domElement);
    this.controls.enableDamping = true; //阻尼
    this.controls.dampingFactor = 0.1; //阻尼

    this.raycaster = new Three.Raycaster();
    this.pointer = new Three.Vector2(-2, -2); //初始化在[-1,1]以外

    this.evaluator = new Evaluator();
    this.evaluator.useGroups = false;

    this.addThings();
    this.addLight(); //+光源
    this.animate(); //开始动
    this.initEventListen();

    this.initGui();
  };

  public destroy = () => {
    this.toggleGui(0);
    Object.values(this.Geometrys).forEach((e) => {
      e.dispose();
    });
    Object.values(this.Materials).forEach((e) => {
      e.dispose();
    });
    this.scene.clear();
    this.renderer.dispose();
    this.renderer.forceContextLoss();
    this.camera = null;
    this.scene = null;
    this.renderer = null;
    this.GuiInit = null;
    window.removeEventListener('resize', this.onWindowResize);
    cancelAnimationFrame(this.animateFrame);
  };

  public initGui = () => {
    if (this.GuiInit) {
      this.toggleGui();
      return;
    }
    this.GuiInit = new dat.GUI({
      // autoPlace: false
    });

    this.GuiInit.domElement.style.position = 'absolute';
    this.GuiInit.domElement.style.right = '10px';
    this.GuiInit.domElement.style.top = '10px';

    let GuiController = this.GuiInit.addFolder('控制窗口');
    GuiController.add(this.guiData, 'x', -0.1, 0.1, 0.005).name('自转速度');
    // GuiController.addColor(this.guiData, 'color1').name('背景颜色');
    GuiController.open();
  };

  public toggleGui = (state: any = null) => {
    if (!this.GuiInit || !this.GuiInit.domElement) {
      return;
    }
    let display = this.GuiInit.domElement.style.display == 'none' ? '' : 'none';
    if (state == 0) {
      display = 'none';
    }
    this.GuiInit.domElement.style.display = display;
  };

  private clickState = '';
  private initEventListen = () => {
    this.container.addEventListener('pointermove', this.onPointerMove);
    this.container.addEventListener(
      'mousedown',
      () => {
        this.clickState = 'down';
      },
      false,
    );
    this.container.addEventListener(
      'mousemove',
      () => {
        this.clickState = '';
      },
      false,
    );
    this.container.addEventListener(
      'mouseup',
      () => {
        if (this.clickState == 'down') {
          this.clickState = '';
          this.onPointClick();
        }
      },
      false,
    );
    window.addEventListener('resize', this.onWindowResize);
  };
  private onPointerMove = (event) => {
    if (event.isPrimary === false) return;
    // this.mouseData.mouseX = event.clientX - this.container.clientWidth;
    // this.mouseData.mouseY = event.clientY - this.container.clientHeight;
    this.mouseData.mouseX = event.clientX - this.container.offsetLeft;
    this.mouseData.mouseY = event.clientY - this.container.offsetTop;
    var scrollTop = window.pageYOffset || document.documentElement.scrollTop; //在container中的位置要考虑scroll

    // 将鼠标位置归一化为设备坐标。x 和 y 方向的取值范围是 (-1 to +1)
    this.pointer.x =
      (this.mouseData.mouseX / this.container.clientWidth) * 2 - 1;
    this.pointer.y =
      -((this.mouseData.mouseY + scrollTop) / this.container.clientHeight) * 2 +
      1;
    // console.log('this.pointer', event.clientY, this.container.offsetTop, )
  };
  private onPointClick = () => {
    console.log('click');
    this.toggleTween();
  };
  private onWindowResize = () => {
    let width = this.container.clientWidth,
      height = this.container.clientHeight;

    this.camera.aspect = width / height;
    this.camera.updateProjectionMatrix();

    if (this.renderer) {
      this.renderer.setSize(width, height);
    }
  };

  private addLight = () => {
    this.lights.ambiLigth = new Three.AmbientLight(0xffffff, 0.8);
    this.scene.add(this.lights.ambiLigth);

    this.lights.pointLight = new Three.PointLight(0xffffff, 5, 5); //intensity, distance, decay
    this.lights.pointLight.position.set(0, 0, 2);
    // this.lights.pointLight.intensity = 0.6;
    this.scene.add(this.lights.pointLight);

    this.lights.directLight = new Three.DirectionalLight(0xffffff, 0.6); //intensity
    this.lights.directLight.position.set(-10, 10, 25);
    this.lights.directLight.castShadow = true;
    this.lights.directLight.shadow.radius = 6; //设置阴影贴图模糊度
    this.scene.add(this.lights.directLight);

    // const hemiLightHelper = new Three.LightProbe( spotLight, 10 );
    // this.scene.add( hemiLightHelper );
    // const hemiLightHelper2 = new Three.HemisphereLightHelper( light, 10 );
    // this.scene.add( hemiLightHelper2 );
  };

  private addThings = () => {
    // const axesHelper = new Three.AxesHelper(800);
    // this.scene.add(axesHelper); //添加坐标轴；红线是X轴，绿线是Y轴，蓝线是Z轴

    /** 接收平面 **/
    const geometry = new Three.PlaneGeometry(bgW, bgH);
    const material = new Three.MeshBasicMaterial({ color: 0xffff00 });
    this.Meshs.raycasterPlatform = new Three.Mesh(geometry, material);
    this.Meshs.raycasterPlatform.visible = false;
    this.scene.add(this.Meshs.raycasterPlatform);
    /** 接收平面 **/

    this.buttonGroup = new Three.Group();
    this.addRoundedRectShape();
    this.addBall();
    this.addClouds();
    this.addStars();
  };

  private addRoundedRectShape = () => {
    const trackShape = new Three.Shape();
    (function roundedRect(ctx, x, y, width, height) {
      //创建跑道形shape，两边圆半径 = height/2
      const radius = height * 0.5;
      const w = width > height ? width - height : 0; //中间部分宽度
      ctx.moveTo(x + radius, y + height);
      ctx.lineTo(x + radius + w, y + height);
      ctx.absarc(
        x + radius + w,
        y + radius,
        radius,
        Math.PI / 2,
        -Math.PI / 2,
        true,
      ); //x,y,radius,startAngle,endAngle,clockwise
      ctx.lineTo(x + radius, y);
      ctx.absarc(
        x + radius,
        y + radius,
        radius,
        -Math.PI / 2,
        Math.PI / 2,
        true,
      );
    })(trackShape, 0, 0, btnW, btnH);

    const extrudeSettings = {
      depth: btnH * 0.05,
      bevelEnabled: true,
      bevelSegments: 10,
      steps: 1,
      bevelSize: btnH * 0.05,
      bevelThickness: btnH * 0.05,
    };

    let geometry = new Three.ExtrudeGeometry(trackShape, extrudeSettings);
    this.Materials.btnMaterial = new Three.MeshPhongMaterial({
      color: this.guiData.btnCo1,
      wireframe: false,
      // side: Three.DoubleSide
    });
    this.Materials.btnMaterial.shininess = 10;
    // this.Materials.btnMaterial.needsUpdate = true; // 启用阴影接收
    const mesh = new Three.Mesh(geometry, this.Materials.btnMaterial);
    mesh.receiveShadow = true;
    mesh.castShadow = true;
    // mesh.position.set( x, y, z - 175 );
    // mesh.rotation.set( rx, ry, rz );
    // mesh.scale.set( s, s, s );

    this.buttonGroup.add(mesh);
    this.buttonGroup.position.set(-btnW * 0.5, -btnH * 0.5, 0);
    this.scene.add(this.buttonGroup);
  };
  private addBall = () => {
    this.Geometrys.ballGeometry = new Three.SphereGeometry(btnH * 0.37, 20, 20);
    // this.Materials.ballMaterial = new Three.MeshBasicMaterial( { color: 0x201f33, overdraw: 0.5 } );
    this.Materials.ballMaterial = new Three.ShaderMaterial({
      uniforms: {
        val: { value: 0.0 },
        color1: {
          value: new Three.Color('#fae2c4'),
        },
        color2: {
          value: new Three.Color('#8360af'),
        },
        color3: {
          value: new Three.Color('#a1a2d9'),
        },
        color1_1: {
          value: new Three.Color('#e66739'),
        },
        color2_1: {
          value: new Three.Color('#eb9846'),
        },
        color3_1: {
          value: new Three.Color('#ffe5ae'),
        },
      },
      vertexShader: `
        varying vec2 vUv;
        void main() {
          vUv = uv;
          gl_Position = projectionMatrix * modelViewMatrix * vec4(position,1.0);
        }
      `,
      fragmentShader: `
        uniform vec3 color1;
        uniform vec3 color2;
        uniform vec3 color3;
        uniform vec3 color1_1;
        uniform vec3 color2_1;
        uniform vec3 color3_1;
        uniform float val;
        varying vec2 vUv;
        void main() {
          float y = vUv.y;
          // 发光的强度
          float glowIntensity = 1.5;

          vec3 co1 = mix(color1.rgb * glowIntensity, color1_1.rgb * glowIntensity, val);
          vec3 co2 = mix(color2.rgb * glowIntensity, color2_1.rgb * glowIntensity, val);
          vec3 co3 = mix(color3.rgb * glowIntensity, color3_1.rgb * glowIntensity, val);
          vec3 gradientColor;
          if (y <= 0.5) {
            gradientColor = mix(co1, co2, y * 2.0);
          } else {
            gradientColor = mix(co2, co3, y * 2.0 - 1.0);
          }
          gl_FragColor = vec4(gradientColor, 1.0);
        }
      `,
      // lights: true
      // wireframe: true
    });
    this.Meshs.ball = new Brush(
      this.Geometrys.ballGeometry,
      this.Materials.ballMaterial,
    );
    // this.Meshs.ball = new Three.Mesh( this.Geometrys.ballGeometry, this.Materials.ballMaterial );
    // this.Meshs.ball.position.set( btnH/2, btnH/2, btnH*0.5+10 );
    // mesh.rotation.set( rx, ry, rz );
    // mesh.scale.set( s, s, s );
    // this.buttonGroup.add(this.Meshs.ball)
    // this.scene.add( this.buttonGroup );

    this.brush = new Brush(
      new Three.CylinderGeometry(btnH * 0.32, btnH * 0.32, btnH * 2, 40),
      this.Materials.ballMaterial,
      // new Three.MeshStandardMaterial({
      //   color: 0x80cbc4,

      //   polygonOffset: true,
      //   polygonOffsetUnits: 1,
      //   polygonOffsetFactor: 1,
      // }),
    );
    this.brush.position.set(-btnW * 0.16, btnH * 0.18, 0);
    this.brush.rotation.set(0.5 * Math.PI, 0, 0);
    this.brush.updateMatrixWorld();
  };
  private addClouds = () => {
    let mtlLoader = new MTLLoader();
    mtlLoader.load('../models/cloud.mtl', (materials) => {
      materials.preload();
      let objLoader = new OBJLoader();
      objLoader.setMaterials(materials);
      objLoader.load(
        '../models/cloud.obj',
        (object) => {
          object.traverse((child) => {
            if (child instanceof Three.Mesh) {
              // child.material.side = Three.DoubleSide;
              // const mtl = new Three.MeshPhongMaterial({ color: 0xaeaeae });
              // mtl.shininess = 10.01
              // child.material = mtl;

              child.material.shininess = 0.001;
              child.material.reflectivity = 0.1;
              object.receiveShadow = true;
              object.castShadow = true;
            }
          });
          console.log('object', object);
          object.position.set(-2, 0, 1);
          // object.scale.set(0.8,0.8,0.8);
          object.receiveShadow = true;
          object.castShadow = true;
          this.Meshs.clouds = object;
          this.scene.add(this.Meshs.clouds);
        },
        (xhr) => {
          console.log((xhr.loaded / xhr.total) * 100 + '% loaded');
        },
        (error) => {
          console.log(error);
        },
      );
    });
  };
  private addStars = () => {
    let mtlLoader = new MTLLoader();
    mtlLoader.load('../models/stars.mtl', (materials) => {
      materials.preload();
      let objLoader = new OBJLoader();
      objLoader.setMaterials(materials);
      objLoader.load(
        '../models/stars.obj',
        (object) => {
          object.traverse((child) => {
            if (child instanceof Three.Mesh) {
              child.material.shininess = 0.001;
              child.material.reflectivity = 0.1;
              object.receiveShadow = true;
              object.castShadow = true;
            }
          });
          console.log('stars', object);
          object.position.set(2, 0, 1);
          // object.scale.set(0.8,0.8,0.8);
          object.receiveShadow = true;
          object.castShadow = true;
          this.Meshs.stars = object;
          this.scene.add(this.Meshs.stars);
        },
        (xhr) => {
          console.log((xhr.loaded / xhr.total) * 100 + '% loaded');
        },
        (error) => {
          console.log(error);
        },
      );
    });
  };

  private updateCSG = () => {
    this.resultBrush = this.evaluator.evaluate(
      this.Meshs.ball,
      this.brush,
      SUBTRACTION,
      // ADDITION,
      this.resultBrush,
    );
    this.resultBrush.material = this.Materials.ballMaterial; //useGroups = false，需要重新上材质
    this.resultBrush.castShadow = true;
    this.resultBrush.receiveShadow = true;
    this.scene.add(this.resultBrush);
  };

  public toggleTween = (callback?: () => void) => {
    // console.log('this.resultBrush:', this.resultBrush)

    this.buttonState = this.buttonState == 0 ? 1 : 0;
    const tween = new TWEEN.Tween(this.ballPosition)
      .to(
        {
          x: this.buttonState == 1 ? btnH / 2 : -btnH / 2,
          z: btnH * 0.5 + 0.1,
        },
        800,
      )
      .start()
      .onComplete(() => {
        if (callback) {
          callback();
        }
      });
    tween.easing(TWEEN.Easing.Back.InOut);
    // tween.easing(TWEEN.Easing.Circular.Out);

    const tweeen2 = new TWEEN.Tween(this.buttonAniVal)
      .to({ val: this.buttonState }, 800)
      .easing(TWEEN.Easing.Quadratic.InOut)
      .onUpdate((res) => {
        this.Materials.ballMaterial.uniforms.val.value = res.val;
      })
      .start();
  };

  public restartAnimate = () => {
    this.stopState = false;
    this.animate();
  };
  public stopAnimate = () => {
    this.stopState = true;
  };
  private stopState: boolean = false;
  private animate = () => {
    if (this.stopState) {
      return;
    }
    // if(!this.renderer){return;}
    this.animateFrame = requestAnimationFrame(this.animate);
    this.render();
    TWEEN.update();
    this.controls.update();
  };
  private render = () => {
    if (!this.renderer) {
      return;
    }

    this.raycaster.setFromCamera(this.pointer, this.camera);
    this.renderer.setClearColor(
      'hsl(248.57, 11.86%, ' + (23 + this.buttonAniVal.val * 20) + '%)',
      1.0,
    );
    if (this.guiData.autoRotate) {
      // this.starsGroup.rotation.x+= 0.025
      // this.starsGroup.rotation.y += 0.002;
    }
    // 射线
    const intersects = this.raycaster.intersectObjects(
      [this.Meshs.raycasterPlatform],
      false,
    );
    if (intersects[0]) {
      // console.log('intersects:', intersects[0].uv)  //左下角0,0 右上角1,1
      const x = intersects[0].uv.x;
      const y = intersects[0].uv.y;
      const posX = (x - 0.5) * bgW;
      const posY = (y - 0.5) * bgH;
      this.lights.pointLight.position.x = posX;
      this.lights.pointLight.position.y = posY;
    }

    this.updateCSG();
    // this.Meshs.ball.rotation.set(0,0,-2)
    this.Meshs.ball.rotation.set(0, 0, this.buttonAniVal.val - 2);
    this.Meshs.ball.position.set(this.ballPosition.x, 0, this.ballPosition.z);
    this.Meshs.ball.updateMatrixWorld();
    const sca = this.buttonAniVal.val * 0.8;
    if (this.Meshs.clouds) {
      this.Meshs.clouds.scale.set(sca, sca, sca);
    }
    const sca1 = (1 - this.buttonAniVal.val) * 0.5;
    if (this.Meshs.stars) {
      this.Meshs.stars.scale.set(sca1, sca1, sca1);
    }

    this.Materials.btnMaterial.color.set(
      new Three.Color().lerpColors(
        this.guiData.btnCo1,
        this.guiData.btnCo2,
        this.buttonAniVal.val,
      ),
    );

    /** lights **/
    // this.lights.ambiLigth.intensity = 0.8 + this.buttonAniVal.val * 1;
    this.lights.pointLight.intensity = 5 - this.buttonAniVal.val * 0.8;
    this.lights.directLight.intensity = 0.6 + this.buttonAniVal.val * 0.2;
    /** lights **/

    this.scene.backgroundBlurriness = 0.4 - this.buttonAniVal.val * 0.25; //模糊
    this.scene.backgroundIntensity = 0.2 + this.buttonAniVal.val * 0.8; //亮度

    this.renderer.render(this.scene, this.camera);
  };
}

const webGlControl = new WebGlControl();
export default webGlControl;
